<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<title>Edit</title>
<style type="text/css">
body{font-family: Arial; font-size: small; background-color: #ccf;}
#outline {margin:20px; border:solid 10px #9FB6CD; -moz-border-radius:20px; width:512px; height:440px;}
#map{width:512px; height:440px;float:left;}
#forehead{text-align:left;font-size:150%;}
#novel{width:400px; margin:20px;float:right;}
#AdSense{margin:20px;}
A:hover {color: red;text-decoration: underline overline;}
td{vertical-align:top;}
.draggable-popup input{background-color:#ccf}
.draggable-popup{background-color:white; border:1px solid black}
</style>

<script 
src="http://maps.google.com/maps?file=api&v=2&key=ABQIAAAAYxpy0HiKBWXiyhVrpVqkshTzRM_GTxWOqTaXXRScn8KwUCiJWhT96ZS2MOLTq-SEmmJL1_cNrib9tA" type="text/javascript">
</script>

</head>

<body onunload="GUnload()">
<table><tr><td>
<div id="forehead"><h3 id="head"> 
Esa's Google Maps API experiments.</h3>
</div></td>
<td rowspan="2">
<div id="novel">
<h4>Polyline editor with CSV output</h4>
<p>Api v2.111 gave us a nifty option to edit GPolylines MyMaps way.</p>
Search your start location: 
<div class="inputsection">
<form action="#" onsubmit="showAddress(this.haku.value,this.haku.id); return false">
<input type="text" size="30" id="haku" name="haku" title="Placename or address"/>
<input type="submit" id="hae" value=" >> " title="Set zoom first"/>
</form>
<form action="#" onsubmit="getDirections(); return false">
<input type="text" size="30" id="dest" name="dest" title="Destination address"/>
<input type="submit" id="directions" value=" >> " title="Driving directions"/>
</form>
</div>
<h4>Draggable Context Menu</h4>
<p>'singlerightclick' opens a popup menu that is a GDraggableObject</p>
<h4>CSV vertex list </h4>
<p><abbr title="Comma Separated Values">CSV</abbr> formatted text files have been used for placing markers but why not for polylines too.</p>
<p>The format <code>lng,lat</code> (longitude first) comes from GPS equipment csv 'standards'.</p>
<p><span id="vertices"></span>The text field updates by map 'dblclick'.
<p><input type="checkbox" checked="true" id="update" title="Uncheck to keep text field as a backup"> update by 'lineupdated' too.
<p><a href="javascript:$('memo').select()">select all</a> and copy/paste where it belongs.
</p>
<textarea id="memo" cols="40" rows="15" title="Memo"></textarea>

<input type="button" value="Draw from text field"  onclick="drawFromText()" />
<p>When you came back later to edit a file you created earlier:</p>
<p>Paste your file to the text field and click [Draw from text field]</p>
<p>Rightclick on map and select [Go on editing]</p>
<p>A comment to be dragged on text field: 
<input type="text" size="3" value=" <!--   -->" onmouseover="this.select()" 
onmouseout="this.value=' <!--   -->'"/></p>

<p><a href="http://koti.mbnet.fi/ojalesa/exam/index.html" target="_blank">Experiment index</a></p>

</div>
</td>
</tr>
<tr>
<td>
<div id="outline">

<div id="map">Map coming...
<noscript>You should turn on JavaScript</noscript>

</div>
</div>


<div id="AdSense">
<script type="text/javascript"><!--
google_ad_client = "pub-3649938975494252";
google_ad_width = 468;
google_ad_height = 60;
google_ad_format = "468x60_as";
google_ad_type = "text_image";
google_ad_channel ="2676021345";
google_color_border = "CCCCFF";
google_color_bg = "CCCCFF";
google_color_link = "0000CC";
google_color_url = "008000";
google_color_text = "000000";
//--></script>

<script type="text/javascript"
  src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
</script>
</div>

</td>
</tr>

</table>


<script type="text/javascript">
//<![CDATA[

///Esa May 2008, updated Oct 2008

if (!GBrowserIsCompatible()) {
  alert('Sorry. Your browser is not Google Maps compatible.');
}

/**
 * A general helper function
 * $() replaces document.getElementById()
 */
if(typeof($)=='undefined'){
  function $(elementId){
    var element = document.getElementById(elementId);
    return element;
  }
}

/**
 * map with GDirections
 * pointer marker
 */ 
_mPreferMetric=true;
var map = new GMap2($("map"));
var start = new GLatLng(65,26);
map.setCenter(start, 5);
map.addControl(new GLargeMapControl());
map.addControl(new GScaleControl(250));
map.addControl(new GMapTypeControl(1));
map.disableDoubleClickZoom();
map.openInfoWindowHtml(map.getCenter(),"Nice to see you.");
map.closeInfoWindow; //preload module;
gdir = new GDirections();
GEvent.addListener(gdir, "load", drawRoute);
GEvent.addListener(gdir, "error", errorReport);


/**
 * A general helper function for creating html elements. <div> as default element type
 * @author Esa 2008
 */
function createElem(opt_className, opt_html, opt_tagName) {
  var tag = opt_tagName||"div";
  var elem = document.createElement(tag);
  if (opt_html) elem.innerHTML = opt_html;
  if (opt_className) elem.className = opt_className;
  return elem;
}

/**
 * draggable popup
 * @author Esa 2008
 */ 
function draggablePopup(contents, opt_options){
  var opts = opt_options||{};
  var popup = createElem("draggable-popup");
  popup.style.width = opts.width||"240px";
  popup.style.top = opts.top||"240px";
  popup.style.left = opts.left||"240px";
  popup.style.padding = opts.padding||"10px";
  popup.style.paddingTop = "0px";
  popup.style.visibility = "hidden";
  var me = this;
  popup.onmouseover = function(){GEvent.trigger(me,'mouseover')};
  popup.onmouseout = function(){GEvent.trigger(me,'mouseout')};
  var header  = createElem("draggable-popup-header");
  header.style.textAlign = "right";
  var closetext = opts.closeText||"Close[x]";
  var close = createElem("draggable-popup-close",closetext,"a");
  close.onclick = function(){popup.style.visibility = "hidden"};
  header.appendChild(close);
  var popupContents = createElem("draggable-popup-contents",contents);
  popup.appendChild(header);
  popup.appendChild(popupContents);
  document.getElementsByTagName("body")[0].appendChild(popup);
  this.dragObject = new GDraggableObject(popup);
  this.popup = popup;
  this.show =  function(){popup.style.visibility = "visible"};
  this.hide =  function(){popup.style.visibility = "hidden"};
}

var html = '<p><input type="button" value="End editing"'
+ 'onclick="poly.disableEditing(); map.closeInfoWindow();" />'
+ '<input type="button" value="Go on editing"'
+ 'onclick="poly.enableEditing();map.closeInfoWindow();" /></p>'

+ '<p><input type="button" value="Delete first" onclick="poly.deleteVertex(0)" />'
+ '<input type="button" value="Delete last"'
+ 'onclick="try{poly.deleteVertex(poly.getVertexCount()-1)}catch(e){}" />'
+ '<input type="button" value="Delete node:" onclick="deleteNode()" />'
+ '<select id="vertex-select" onchange="showNode(this.selectedIndex)"></select></p>'

+ '<p><input type="button" value="New rectangle"  onclick="rectPoly()" />'
+ '<br/><input type="button" value="New polyline from scratch"  onclick="drawPoly()" /></p>'

+ '<p><input type="text" id="km" size="1" value="1" onmouseover="this.focus()"/>km'
+ '<input type="button" value="New circle" onclick="drawCircle()"/></p>';

var context = new draggablePopup(html);
GEvent.addListener(map, "singlerightclick", function(){
  context.show();
});   //these are needed to keep draggable popup stable when using selector
GEvent.addDomListener($('vertex-select'), "mouseover", function(){
  context.dragObject.disable();
});
GEvent.addDomListener($('vertex-select'), "mouseout", function(){
  context.dragObject.enable();
});



/**
 * Selector options handling
 */
var selector = $('vertex-select');
function addOption(value){
  var len = selector.options.length;
  selector.options[len] = new Option(value);
}
function clearSelector(){
  while(selector.options[0]){
    selector.options[0] = null;
  }
}
function deleteNode(){
  poly.deleteVertex(selector.selectedIndex);
}
function showBlowup(){
  var sel_ = selector.selectedIndex||0;
  try{map.showMapBlowup(poly.getVertex(sel_))}catch(e){};
}
function showNode(index_){
  map.setCenter(poly.getVertex(index_));
  showBlowup();
}

/**
* GLatLngBounds.drawBox() method
* Returns a GPolyline or GPolygon rectangle representing the bounds
* optional parameter is an object with style properties {liColor, liWidth, liOpa, fillColor, fillOpa, polygon}
* {polygon:true} switches the return object from GPolyline to GPolygon
* @author Esa 2007
 */
GLatLngBounds.prototype.drawBox = function(opt_options){
  var opts = opt_options||{};
  var northEast = this.getNorthEast();
  var southWest = this.getSouthWest();
  var topLat = northEast.lat();
  var rightLng = northEast.lng();
  var botLat = southWest.lat();
  var leftLng = southWest.lng();
  var pnts = [];
  pnts.push(southWest);
  pnts.push(new GLatLng(topLat,leftLng));
  pnts.push(northEast);
  pnts.push(new GLatLng(botLat,rightLng));
  pnts.push(southWest);
  var fillColor = opts.fillColor||opts.liColor||"#0055ff";
  var liWidth = opts.liWidth||2;
  if(opts.polygon){
    var boxPoly = new GPolygon(pnts,opts.liColor,liWidth,opts.liOpa,fillColor,opts.fillOpa);
  }else{
    var boxPoly = new GPolyline(pnts,opts.liColor,liWidth,opts.liOpa);
  }
  return boxPoly;
}


/**
* GLatLng.drawKmCircle() method
* Returns a GPolyline or GPolygon rectangle representing 
* optional parameter is an object with radius, quality and style properties {radius, nodes, liColor, liWidth, liOpa, fillColor, fillOpa, polygon}
* {polygon:true} switches the return object from GPolyline to GPolygon
* @author Esa 2006, 2008
 */
GLatLng.prototype.drawKmCircle =function (opt_options){
  var pnts = [];
  var opts = opt_options||{};
  var radius = 1*opts.radius||1;
  var step = parseInt(360/opts.nodes)||18;
  var latConv = this.distanceFrom(new GLatLng(this.lat()+0.1, this.lng()))/100;
  var lngConv = this.distanceFrom(new GLatLng(this.lat(), this.lng()+0.1))/100;
  for(var i=0; i<=360; i+=step)
    { // @author Esa 2006, 2008
    var pint = new GLatLng(this.lat() + (radius/latConv * Math.cos(i * Math.PI/180)), 
      this.lng() + (radius/lngConv * Math.sin(i * Math.PI/180)));
    pnts.push(pint);
  }
  var fillColor = opts.fillColor||opts.liColor||"#0055ff";
  var liWidth = opts.liWidth||2;
  if(opts.polygon){
    var poly = new GPolygon(pnts,opts.liColor,liWidth,opts.liOpa,fillColor,opts.fillOpa);
  }else{
    var poly = new GPolyline(pnts,opts.liColor,liWidth,opts.liOpa);
  }
  return poly;
}


/**
 * Context menu button functions
 */
var poly = null;
var updateListener;
function rectPoly(){
  map.clearOverlays();
  updateListener = null;
  map.zoomIn();
  poly = map.getBounds().drawBox();
  map.addOverlay(poly);
  map.zoomOut();
  poly.enableEditing();
  setListener(poly);
  listPoly();
}

function drawPoly(){
  map.clearOverlays();
  updateListener = null;
  poly = new GPolyline([],"blue",2);
  map.addOverlay(poly);
  poly.enableDrawing();
  setListener(poly);
}

function drawCircle(){
  var km = $("km").value;
  map.clearOverlays();
  updateListener = null;
  poly = map.getCenter().drawKmCircle({radius:km, polygon: true});
  map.addOverlay(poly);
  poly.enableEditing();
  setListener(poly);
  map.fit(poly.getBounds(), true);
  listPoly();
}

function getDirections(){
  var saddr = $("haku").value;
  var daddr = $("dest").value;
  gdir.load("from: " + saddr + " to: " + daddr,{getPolyline:true});
}  

function drawRoute(){
  map.clearOverlays();
  updateListener = null;
  poly = gdir.getPolyline();
  var vert = poly.getVertexCount();
  var permission = true;
  if(vert>100) permission = confirm(vert + " points are not editable but I can draw it.");
  if(!permission)return;
  if(vert>1000) permission = confirm(vert + " points is much. Are you really serious?");
  if(!permission)return;
  if(vert>10000) permission = confirm(vert + " points. This is your last change. Proceed?");
  if(permission){
    map.addOverlay(poly);
    setListener(poly);
    map.fit(poly.getBounds(), true);
    listPoly();
  }
}

/**
 * Draw a polyline from textarea data
 */
function drawFromText(){
  map.clearOverlays();
  updateListener = null;
  var polyPoints = $("memo").value.parseCsv();
  poly = new GPolyline(polyPoints,"red",2);
  map.addOverlay(poly);
  var bounds = poly.getBounds();
  if(bounds)map.fit(bounds, true);
  setListener(poly);
}


/**
 * Update textarea by polyline 'lineupdated' and map 'dblclick'
 */
function setListener(obj){
    updateListener = GEvent.addListener(obj, "lineupdated", function(){
    if($("update").checked) listPoly();
  });
  GEvent.addListener(map, "dblclick", function(){
    listPoly();
  });
}
function listPoly(){
  clearSelector();
  var rows = [];
  $("memo").value = "";
  if(poly){
    var len = poly.getVertexCount()||0;
    for(var i=0; i<len; i++){
      var point = poly.getVertex(i);
      rows.push(point.lng().toFixed(6)+", "+point.lat().toFixed(6));
	  addOption(i);
	}
    $("memo").value = rows.join('\n');
  }
  $("vertices").innerHTML = len + " vertices . ";
}


/**
 * Geocoder
 */
var geocoder = new GClientGeocoder();
function showAddress(address,id){
  geocoder.getLatLng(address,function(point){
    if(!point){
      $(id).style.color = "#ff0000";
    }else{
      map.setCenter(point);
      rectPoly();
      $(id).style.color = "#000000";
      $("memo").value = "";
    }
  })
}


/**
 * zoom and pan to fit in view
 */
GMap2.prototype.fit = function(bounds,save){
  if(!save){
    this.setZoom(map.getBoundsZoomLevel(bounds));
    this.panTo(bounds.getCenter()); 
  }else{
    this.setCenter(bounds.getCenter(), this.getBoundsZoomLevel(bounds));
    this.savePosition();
  }
}

/**
 * parseCsv()
 * @return an array of GLatLng() objects
 * @author Esa 2008
 */
String.prototype.parseCsv = function(opt_options){
  var results = [];
  var opts = opt_options||{};
  var iLat = opts.lat||1;
  var iLng = opts.lng||0;
  var lines = this.split("\n");
  for (var i=0; i<lines.length; i++) {
    var blocks = lines[i].split('"');
    //finding commas inside quotes. Replace them with '::::'
	for(var j=0;j<blocks.length;j++){
      if(j%2){
        blocks[j]=blocks[j].replace(/,/g,'::::');
      }
    }  //@author Esa 2008, keep this note.
    lines[i] = blocks.join("");
    var lineArray = lines[i].split(",");
    var lat = lineArray[iLat]*1;
    var lng = lineArray[iLng]*1;
    var point = new GLatLng(lat,lng);
    //after splitting by commas, we put hidden ones back
	for(var cell in lineArray){
      lineArray[cell] = lineArray[cell].replace(/::::/g,',');
    } //corrupted line step-over
	if(!isNaN(lat+lng)){
	  point.textArray = lineArray;
	  results.push(point);
    }
  }
  return results;
}


/**
 * directions error
 */
 function errorReport(){
 alert("D'oh");
 }
 
/**
 * Draw rectangle on page load
 */
rectPoly();

window.onload = function(){
  listPoly();
}
//]]>
</script>

</body>
</html>